//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-01-15     ZhangYihua   first version
//
// Description  : 
//################################################################################

// cell fifo controller
module cfifo_ctrl #(
parameter           CDATA_BW                = 18,       // cell data bit width
parameter           CLEN_MAX                = 64,       // the longest cell length <= CLEN_MAX < MEM_DEPTH - WRAP_NUM_ADAE*2
parameter           MEM_DEPTH               = 512,      // it must be even if ASYNC_CLK==1'b1, actural maximum wr_used is MEM_DEPTH-1
parameter           WRAP_NUM_ADAE           = 0,        // the number of added data after EOC which will be wrapped to head of cell
                                                        // 0:no added data after EOC, all data is written to mem sequentilly;
                                                        // x(x>=1):x added data after EOC is promoted ahead of original head of cell;
parameter           ASYNC_CLK               = 1'b0,     // clock of read and write is 1'b0:synchronous; 1'b1:asynchronous
parameter           SYNC_NUM_W2R            = 2,
parameter           SYNC_NUM_R2W            = 2,
parameter           GRAY_ADDR_LPL           = 0,        // if RAPL=WAPL=0, let GRAY_ADDR_LPL=0, else 1
parameter           RDPL_NUM                = 2,        // read operation pipeline steps, include read address and data pipeline
// rapl:    read address pipeline means pipeline steps from mem_raddr to SRAM, 0 means no DFF between them
// wapl:    write address pipeline means pipeline steps from mem_waddr to SRAM, 0 means no DFF between them
// it's required that rapl must equals to wapl at any case.
// case 1: rapl=wapl=0, normal operation is ok;
// case 2: rapl=wapl=1 with prefetch mode 'RDPL_CACHE', normal operation is ok;
// case 3: rapl=wapl=1 with prefetch mode 'RDPL_SHIFT' or 'RDPL_STUFF', let DEPTH = theory_estimation + 1, and
//                     take afull as back pressure to avoid SRAM to overflow;
// case others: not recommended;
parameter           ARCH_MODE               = "RDPL_SHIFT", // the smallest area
//parameter           ARCH_MODE               = "RDPL_STUFF", // no bubble exist, but memory must support individul CE for each RDPL step
//parameter           ARCH_MODE               = "RDPL_CACHE", // no bubble exist, but more cache REGs comsumed, about (RDPL_NUM+2)*DATA_BW

// the following parameters are calculated automatically
parameter           WRAP_CNT_BW             = (WRAP_NUM_ADAE<=2) ? 1 : $clog2(WRAP_NUM_ADAE),
parameter           ADDR_BW                 = $clog2(MEM_DEPTH)     // address range [0:MEM_DEPTH-1]
) ( 
input                                       rst_wr_n,
input                                       clk_wr,

output                                      wr_rdy,     // 1:local side is ready for taking cell data; 0:not ready
input                                       wr_vld,
input               [CDATA_BW-1:0]          wr_data,    // both wr_vld and wr_rdy asserting means one successful transfering cell data
input                                       wr_eoc,     // end of cell, valid when wr_vld==1'b1, don't care when others
input                                       wr_drop,    // high pulse anywhere regardless of wr_vld means to drop current cell

// unused if WRAP_NUM_ADAE=0
output                                      wrap_rdy,
output              [WRAP_CNT_BW-1:0]       wrap_cnt,   // increase from 0 to WARP_NUM_ADAE-1
output                                      wrap_end,
input               [CDATA_BW-1:0]          wrap_data,
input               [WRAP_CNT_BW-1:0]       wrap_ofst,  // offset address for wrap_data, within [0:WRAP_NUM_ADAE-1]
input                                       wrap_drop,  // high pulse after wr_eoc but before wrap_end(included) means to drop current cell

input               [ADDR_BW-1:0]           cfg_afull_th,
output  reg         [ADDR_BW-1:0]           wr_cnt,     // 0 <= wr_cnt <= MEM_DEPTH-1
output  reg                                 wr_afull,   // wr_afull assert when wr_cnt>cfg_afull_th
output                                      wr_err,     // cell data is over length and droped

input                                       rst_rd_n,
input                                       clk_rd,

input                                       rd_rdy,     // 1:peer side is ready for taking cell data; 0:busy
output                                      rd_vld,     // 1:local side is valid for offerring cell data(at least a completed cell); 0:empty
output              [CDATA_BW-1:0]          rd_data,    // both rd_vld and rd_rdy asserting means one successful transfering  cell data
output                                      rd_eoc,

output                                      mem_wen,
output              [ADDR_BW-1:0]           mem_waddr,
output              [CDATA_BW+1-1:0]        mem_wdata,

// clock enable for memory read data pipeline 
// RDPL_SHIFT: shift and hold all RDPL REGs by mem_rdpl_ce[0]
// RDPL_STUFF: shift and hold each step of RDPL REGs by individual mem_rdpl_ce[x]
// RDPL_CACHE: mem_rdpl_ce[0] is just for clock gating only, it has nothing to with functionality
output                                      mem_ren,
output              [RDPL_NUM-1:0]          mem_rdpl_ce,
output              [ADDR_BW-1:0]           mem_raddr,
input               [CDATA_BW+1-1:0]        mem_rdata
);

// basic waveform
// (1) cell write side (WRAP_NUM_ADAE=3, n<=CLEN_MAX, 0<=x,y,z<3)
//                               |<-------------------- same cell ----------------------------->|
//                               |<--------------- original data ------------>|<----- WRAP ---->|
//                     _____________________        __________________________                   ______________________
// wr_rdy(o)   :______|                       ~~~~                            |_________________|
//                                                                                                                            
//                                __________        __________________________                          
// wr_vld(i)   :_________________|            ~~~~                            |________________________________________
//              _________________ __________        ___________________________________________________________________
// wr_data(i)  :                 |d1 |d2 |    ~~~~    |   |   |   |   |   |dn |                                         
//              ----------------------------        -------------------------------------------------------------------
//                                                                         ___                                   
// wr_eoc(i)   :____________________________  ~~~~  ______________________|   |________________________________________
//                                                                             _________________
// wrap_rdy(o) :____________________________  ~~~~  __________________________|                 |______________________
//                                                                                         _____                                   
// wrap_end(i) :____________________________  ~~~~  ______________________________________|     |______________________
//              ____________________________        ____________________________________________ ______________________
// wrap_cnt(o) :                              ~~~~                            | 0   | 1   | 2   |                       
//              ----------------------------        -------------------------------------------------------------------
//              ____________________________        ____________________________________________ ______________________
// wrap_data(i):                              ~~~~                            | adx | ady | adz |                       
//              ----------------------------        -------------------------------------------------------------------
//              ____________________________        ____________________________________________ ______________________
// wrap_ofst(i):                              ~~~~                            | x   | y   | z   |                       
//              ----------------------------        -------------------------------------------------------------------
//              _________________ __________        ____________________________________________ ______________________
// mem_waddr   :                 |A+1|A+2|    ~~~~    |   |   |   |   |   |A+n|A-2+x|A-2+y|A-2+z|                       
//              ----------------------------        -------------------------------------------------------------------
//              _________________ __________        ____________________________________________ ______________________
// wr_addr     :                 |A+1|A+2|    ~~~~    |   |   |   |   |   |A+n|A+n+1|A+n+2|A+n+3|                       
//              ----------------------------        -------------------------------------------------------------------
//              _________________ ______________________________________________________________ ______________________
// soc_addr    :                 |           A-2                                                |        A+n+1          
//              -------------------------------------------------------------------------------------------------------

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          FULL_BIN                = ((1<<ADDR_BW)==MEM_DEPTH) ? 1'b1 : 1'b0;
localparam          ADDR_VOID_NUM           = (1<<ADDR_BW)-MEM_DEPTH;
localparam  [ADDR_BW-1:0]   USED_ONE        = 1;
localparam  [ADDR_BW-1:0]   ADDR_MAX        = ((ASYNC_CLK==1'b1) ? ADDR_VOID_NUM/2 : 0) + MEM_DEPTH-1;
localparam  [ADDR_BW-1:0]   ADDR_MIN        = ((ASYNC_CLK==1'b1) ? ADDR_VOID_NUM/2 : 0);
localparam  [ADDR_BW-1:0]   ADDR_GRY_INI    = ADDR_MIN^(ADDR_MIN>>1);

localparam  [ADDR_BW-1:0]   WR_ADDR_INI     = ADDR_MIN+WRAP_NUM_ADAE;
localparam  [ADDR_BW-1:0]   DEPTH_MINUS_1   = MEM_DEPTH-1;
localparam  [ADDR_BW-1:0]   DEPTH_MINUS_2   = MEM_DEPTH-2;

wire                                        wr_one;
reg                                         wr_drop_hld;
wire                                        wr_en;
wire                                        wrap_ind;
wire                                        drop_flg;
wire                [ADDR_BW-1:0]           wr_addr_inc;
reg                 [ADDR_BW-1:0]           wr_addr;
reg                 [ADDR_BW-1:0]           soc_addr;
wire                [ADDR_BW-1:0]           cell_len;
reg                                         clen_err;
wire                [ADDR_BW-1:0]           wr_used;
reg                                         wr_full;
wire                                        fifo_wen;
wire                [ADDR_BW-1:0]           wrap_waddr;

wire                [ADDR_BW-1:0]           rd_used;
wire                [ADDR_BW-1:0]           rd_addr_inc;
reg                 [ADDR_BW-1:0]           rd_addr;
reg                                         raw_fifo_empty;
wire                                        raw_fifo_ren;
wire                                        zrl_fifo_ren;
wire                                        zrl_fifo_empty;
wire                [CDATA_BW+1-1:0]        zrl_fifo_rdata;

//################################################################################
// main
//################################################################################

assign wr_one = wr_rdy & wr_vld;

always@(posedge clk_wr or negedge rst_wr_n)
begin
    if (rst_wr_n==1'b0) begin
        wr_drop_hld<=1'b0;
    end else begin
        if (wr_one==1 && wr_eoc==1'b1)
            wr_drop_hld <=`U_DLY 1'b0;
        else if (wr_drop==1'b1)
            wr_drop_hld <=`U_DLY 1'b1;
        else
            ;
    end
end

generate if (WRAP_NUM_ADAE==0) begin:G_NWRAP

    assign wr_en  = wr_one;
    assign wr_rdy = ~wr_full;
    assign wrap_rdy = wr_rdy;
    assign wrap_end = wr_eoc;
    assign wrap_ind = 1'b0;
    assign wrap_cnt = 1'b0;

    assign drop_flg = wr_drop | wr_drop_hld;

end else begin:G_WRAP
    reg                                         ind;
    reg                                         wrap_drop_hld;

    always@(posedge clk_wr or negedge rst_wr_n) begin
        if (rst_wr_n==1'b0) begin
            ind <=`U_DLY 1'b0;
        end else begin
            if (wr_eoc==1'b1 && wr_one==1'b1) begin
                ind <=`U_DLY 1'b1;
            end else if (wrap_end==1'b1 && wrap_rdy==1'b1) begin
                ind <=`U_DLY 1'b0;
            end
        end
    end
    
    assign wrap_ind  = ind;
    assign wr_en  = wr_one | wrap_rdy;
    assign wr_rdy = (~wr_full) & (~wrap_ind);
    assign wrap_rdy = (~wr_full) & wrap_ind;

    if (WRAP_NUM_ADAE==1) begin:G_1
        assign wrap_end = wrap_ind;
        assign wrap_cnt = 1'b0;
    end else begin:G_N
        reg                 [WRAP_CNT_BW-1:0]       cnt;

        always@(posedge clk_wr or negedge rst_wr_n) begin
            if (rst_wr_n==1'b0) begin
                cnt <=`U_DLY {WRAP_CNT_BW{1'b0}};
            end else begin
                if (wr_eoc==1'b1 && wr_one==1'b1) begin
                    cnt <=`U_DLY {WRAP_CNT_BW{1'b0}};
                end else if (wrap_rdy==1'b1 && wrap_end==1'b0) begin
                    cnt <=`U_DLY cnt + 1'd1;
                end
            end
        end

        assign wrap_end  = (cnt==$unsigned(WRAP_NUM_ADAE-1)) ? wrap_ind : 1'b0;
        assign wrap_cnt  = cnt;
    end

    always@(posedge clk_wr or negedge rst_wr_n)
    begin
        if (rst_wr_n==1'b0) begin
            wrap_drop_hld<=1'b0;
        end else begin
            if (wrap_end==1'b1 && wrap_rdy==1'b1)
                wrap_drop_hld<=`U_DLY 1'b0;
            else if (wrap_ind==1'b1 && wrap_drop==1'b1)
                wrap_drop_hld<=`U_DLY 1'b1;
            else if (wrap_ind==1'b0 && (wr_drop==1'b1 || wr_drop_hld==1'b1))
                wrap_drop_hld<=`U_DLY 1'b1;
            else
                ;
        end
    end

    assign drop_flg = (wrap_ind==1'b0) ? wr_drop | wr_drop_hld : wrap_drop | wrap_drop_hld;

end endgenerate

assign wr_addr_inc = addr_inc_f(wr_addr);
always@(posedge clk_wr or negedge rst_wr_n) begin
    if (rst_wr_n==1'b0) begin
        wr_addr <=`U_DLY WR_ADDR_INI;
    end else begin
        if (clen_err==1'b1 || drop_flg==1'b1) 
            wr_addr <=`U_DLY (WRAP_NUM_ADAE==0) ? soc_addr :
                             (WRAP_NUM_ADAE==1) ? addr_inc_f(soc_addr) : 
                                                  addr_jump_f(soc_addr);
        else if (wr_en==1'b1)
            wr_addr <=`U_DLY wr_addr_inc;
        else
            ;
    end
end

always@(posedge clk_wr or negedge rst_wr_n) begin
    if (rst_wr_n==1'b0) begin
        soc_addr <=`U_DLY ADDR_MIN;
    end else begin
        if (wrap_rdy==1'b1 && wrap_end==1'b1 && clen_err==1'b0 && drop_flg==1'b0)
            soc_addr <=`U_DLY (WRAP_NUM_ADAE==0) ? wr_addr_inc :
                              (WRAP_NUM_ADAE==1) ? wr_addr :
                                                   addr_back_f(wr_addr);
        else
            ;
    end
end

// in any case, cell_len must be less than CLEN_MAX. otherwise maybe dead forever
assign cell_len = fifo_used_f(wr_addr, soc_addr);
always@(posedge clk_wr or negedge rst_wr_n)
begin
    if (rst_wr_n==1'b0) begin
        clen_err<=1'b0;
    end else begin
        if (wrap_rdy==1'b1 && wrap_end==1'b1)
            clen_err<=`U_DLY 1'b0;
        else if (cell_len>=(CLEN_MAX+2*WRAP_NUM_ADAE) || (cell_len>=(CLEN_MAX+2*WRAP_NUM_ADAE-1) && wr_en==1'b1))    // drop over length cell precisely
            clen_err<=`U_DLY 1'b1;
        else
            ;
    end
end
assign wr_err = clen_err;

assign fifo_wen = wr_en & (~drop_flg) & (~clen_err);
always@(posedge clk_wr or negedge rst_wr_n) begin
    if (rst_wr_n==1'b0) begin
        wr_cnt <=`U_DLY {ADDR_BW{1'b0}};
    end else begin
        wr_cnt <=`U_DLY wr_used + fifo_wen;
    end
end

always@(posedge clk_wr or negedge rst_wr_n) begin
    if (rst_wr_n==1'b0) begin
        wr_full <=`U_DLY 1'b0;
    end else begin
        if (wr_used==DEPTH_MINUS_2 && fifo_wen==1'b1)
            wr_full <=`U_DLY 1'b1;
        else if (wr_used>=DEPTH_MINUS_1)
            wr_full <=`U_DLY 1'b1;
        else
            wr_full <=`U_DLY 1'b0;
    end
end

always@(posedge clk_wr or negedge rst_wr_n) begin
    if (rst_wr_n==1'b0) begin
        wr_afull <=`U_DLY 1'b0;
    end else begin
        if (wr_used==cfg_afull_th && fifo_wen==1'b1)
            wr_afull <=`U_DLY 1'b1;
        else if (wr_used>cfg_afull_th)
            wr_afull <=`U_DLY 1'b1;
        else
            wr_afull <=`U_DLY 1'b0;
    end
end

assign wrap_waddr = (WRAP_NUM_ADAE==0 || wrap_ind==1'b0) ?  wr_addr : addr_add_f(soc_addr, wrap_ofst);
                    
assign mem_wen   = fifo_wen;
assign mem_waddr = (ASYNC_CLK==1'b1 && FULL_BIN==1'b0) ? wrap_waddr - ADDR_MIN : wrap_waddr;
assign mem_wdata = (wrap_ind==1'b1) ? {1'b0, wrap_data} : {wr_eoc, wr_data};


generate if (ASYNC_CLK==1'b1) begin:G_ASYNC
    wire                [ADDR_BW-1:0]           raddr_nxt;
    wire                [ADDR_BW-1:0]           raddr2wr_gry;
    wire                [ADDR_BW-1:0]           raddr2wr_s;
    wire                [ADDR_BW-1:0]           raddr2wr_bin;

    wire                [ADDR_BW-1:0]           waddr2rd_s;

    assign raddr_nxt = (GRAY_ADDR_LPL==0 && raw_fifo_ren==1'b1) ? rd_addr_inc : rd_addr;

    bin2gray #(.BW(ADDR_BW), .REG_EN(1'b1), .GRY_INI(ADDR_GRY_INI)) u_r2w_gray (.rst_n(rst_rd_n), .clk(clk_rd), .i_bin(raddr_nxt), .o_gray(raddr2wr_gry));
    sync_dff #(.SYNC_NUM(SYNC_NUM_R2W), .BW(ADDR_BW), .INI(ADDR_GRY_INI)) u_r2w_dff (.rst_n(rst_wr_n), .clk(clk_wr), .d(raddr2wr_gry), .q(raddr2wr_s));
    gray2bin #(.BW(ADDR_BW)) u_r2w_bin (.i_gray(raddr2wr_s), .o_bin(raddr2wr_bin));

    assign wr_used = fifo_used_f(wr_addr, raddr2wr_bin);
    
    sync_vect #(
            .VECT_BW                        (ADDR_BW                        ),
            .VECT_INI                       (ADDR_MIN                       ),
            .SYNC_NUM_D2S                   (SYNC_NUM_R2W                   ),
            .SYNC_NUM_S2D                   (SYNC_NUM_W2R                   )
    ) u_waddr_syn ( 
            .rst_src_n                      (rst_wr_n                       ),
            .clk_src                        (clk_wr                         ),

            .src_pause                      (1'b0                           ),	// keep src_pause 1'b0 as far as possible for robustness
            .src_vect                       (soc_addr                       ),
            .src_busy                       (                               ),	// last vect is uncompleted

            .rst_dst_n                      (rst_rd_n                       ),
            .clk_dst                        (clk_rd                         ),

            .dst_vect                       (waddr2rd_s                     )
    );
    assign rd_used = fifo_used_f(waddr2rd_s, rd_addr);

end else begin:G_SYNC

    assign wr_used = fifo_used_f(wr_addr, rd_addr);
    assign rd_used = fifo_used_f(soc_addr, rd_addr);

end endgenerate

assign rd_addr_inc = addr_inc_f(rd_addr);
always@(posedge clk_rd or negedge rst_rd_n) begin
    if (rst_rd_n==1'b0) begin
        rd_addr <=`U_DLY ADDR_MIN;
    end else begin
        if (raw_fifo_ren==1'b1)
            rd_addr <=`U_DLY rd_addr_inc;
        else
            ;
    end
end

assign mem_ren = raw_fifo_ren;
assign mem_raddr = (ASYNC_CLK==1'b1 && FULL_BIN==1'b0) ? rd_addr - ADDR_MIN : rd_addr;

always@(posedge clk_rd or negedge rst_rd_n) begin
    if (rst_rd_n==1'b0) begin
        raw_fifo_empty <=`U_DLY 1'b1;
    end else begin
        if (rd_used==USED_ONE && raw_fifo_ren==1'b1)
            raw_fifo_empty <=`U_DLY 1'b1;
        else if (rd_used=={ADDR_BW{1'b0}})
            raw_fifo_empty <=`U_DLY 1'b1;
        else
            raw_fifo_empty <=`U_DLY 1'b0;
    end
end

assign zrl_fifo_ren = rd_vld & rd_rdy;
assign rd_vld = ~zrl_fifo_empty;
assign {rd_eoc, rd_data} = zrl_fifo_rdata;

fifo_prefetch #(
        .DATA_BW                        (CDATA_BW+1                     ),
        .RDPL_NUM                       (RDPL_NUM                       ),	// read operation pipeline steps, include read address and data pipeline
        .ARCH_MODE                      (ARCH_MODE                      )
) u_prefetch ( 
        .rst_n                          (rst_rd_n                       ),
        .clk                            (clk_rd                         ),

// raw fifo with read data latency > 0
        .raw_fifo_empty                 (raw_fifo_empty                 ),
        .raw_fifo_ren                   (raw_fifo_ren                   ),

// zero read latency fifo after prefetching
        .zrl_fifo_ren                   (zrl_fifo_ren                   ),
        .zrl_fifo_empty                 (zrl_fifo_empty                 ),
        .zrl_fifo_rdata                 (zrl_fifo_rdata                 ),
        .zrl_fifo_ruf                   (                               ),	// read underflow

// clock enable for memory read data pipeline 
// RDPL_SHIFT: shift and hold all RDPL REGs by mem_rdpl_ce[0]
// RDPL_STUFF: shift and hold each step of RDPL REGs by individual mem_rdpl_ce[x]
// RDPL_CACHE: mem_rdpl_ce[0] is just for clock gating only, it has nothing to with functionality
        .mem_rdpl_ce                    (mem_rdpl_ce                    ),
        .mem_rdata                      (mem_rdata                      ),

        .prefetch_cnt                   (                               )
);

// addr = addr + 1
function [ADDR_BW-1:0] addr_inc_f;
    input [ADDR_BW-1:0] addr;
    begin
        if (FULL_BIN==1'b1)
            addr_inc_f = addr + 1'd1;
        else
            addr_inc_f = (addr>=ADDR_MAX) ? ADDR_MIN : addr + 1'd1;
    end
endfunction

// addr = addr + WRAP_NUM_ADAE;
function [ADDR_BW-1:0] addr_jump_f;
    input [ADDR_BW-1:0] addr;

    reg [ADDR_BW-1:0] t;
    begin
        t = addr + $unsigned(WRAP_NUM_ADAE);
        if (FULL_BIN==1'b1)
            addr_jump_f = t;
        else
            addr_jump_f = (addr>=$unsigned(ADDR_MAX+1-WRAP_NUM_ADAE)) ? addr + $unsigned(WRAP_NUM_ADAE+ADDR_VOID_NUM) : t;
    end
endfunction

// addr = addr - (WRAP_NUM_ADAE - 1);
function [ADDR_BW-1:0] addr_back_f;
    input [ADDR_BW-1:0] addr;

    reg [ADDR_BW-1:0] t;
    begin
        t = addr - $unsigned(WRAP_NUM_ADAE-1);
        if (WRAP_NUM_ADAE==1)
            addr_back_f = addr;
        else if (FULL_BIN==1'b1)
            addr_back_f = t;
        else
            addr_back_f = (addr<=$unsigned(ADDR_MIN-2+WRAP_NUM_ADAE)) ? addr - $unsigned(WRAP_NUM_ADAE-1+ADDR_VOID_NUM) : t;
    end
endfunction

// addr = addr + x; (0<=x<=WRAP_NUM_ADAE)
function [ADDR_BW-1:0] addr_add_f;
    input [ADDR_BW-1:0] addr;
    input [WRAP_CNT_BW-1:0] x;

    reg [ADDR_BW:0] t;
    begin
        t = addr + x;
        if (FULL_BIN==1'b1)
            addr_add_f = t[0+:ADDR_BW];
        else
            addr_add_f = (t>=$unsigned(ADDR_MAX+1)) ? t[0+:ADDR_BW] + $unsigned(ADDR_VOID_NUM) : t[0+:ADDR_BW];
    end
endfunction

function [ADDR_BW-1:0] fifo_used_f;
    input [ADDR_BW-1:0] wadr;
    input [ADDR_BW-1:0] radr;

    begin
        if (FULL_BIN==1'b1)
            fifo_used_f = wadr - radr;
        else
            fifo_used_f = (wadr>=radr) ? wadr - radr : wadr - radr - ADDR_VOID_NUM;
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
